﻿using Common.Core.Cache;
using Microsoft.Extensions.Caching.StackExchangeRedis;
using StackExchange.Redis;
using System.Text;
using System.Text.Json;

namespace Common.Cache.Redis
{
    /// <summary>
    /// Redis缓存操作
    /// </summary>
    public class RedisCacheService : ICacheService
    {
        /// <summary>
        /// 是否是Redis
        /// </summary>
        public bool IsRedis { get; }
        /// <summary>
        /// 
        /// </summary>
        protected IDatabase _database;
        /// <summary>
        /// 
        /// </summary>
        private ConnectionMultiplexer _connection;
        /// <summary>
        /// 
        /// </summary>
        private readonly string _instance;

        private readonly JsonSerializerOptions _jsonOptions;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="options"></param>
        /// <param name="database"></param>
        /// <param name="jsonOptions"></param>
        public RedisCacheService(RedisCacheOptions options, JsonSerializerOptions jsonOptions, int database = 0)
        {
            _connection = ConnectionMultiplexer.Connect(options.Configuration);
            _database = _connection.GetDatabase(database);
            _instance = options.InstanceName;
            _jsonOptions = jsonOptions;
            IsRedis = true;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public string GetKeyForRedis(string key)
        {
            return _instance + key;
        }
        #region 验证缓存项是否存在
        /// <summary>
        /// 验证缓存项是否存在
        /// </summary>
        /// <param name="key">缓存Key</param>
        /// <returns></returns>
        public bool Exists(string key)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }
            return _database.KeyExists(GetKeyForRedis(key));
        }
        /// <summary>
        /// 验证缓存项是否存在
        /// </summary>
        /// <param name="key">缓存Key</param>
        /// <returns></returns>
        public Task<bool> ExistsAsync(string key)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }
            return _database.KeyExistsAsync(GetKeyForRedis(key));
        }
        #endregion

        #region 添加缓存
        /// <summary>
        /// 添加缓存
        /// </summary>
        /// <param name="key">缓存Key</param>
        /// <param name="value">缓存Value</param>
        /// <returns></returns>
        public bool Add(string key, object value)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }
            string _value = "";
            if(value.GetType() == typeof(string))
                _value = value.ToString();
            else
                _value = JsonSerializer.Serialize(value, _jsonOptions);
            return _database.StringSet(GetKeyForRedis(key), Encoding.UTF8.GetBytes(_value));
        }
        /// <summary>
        /// 添加缓存
        /// </summary>
        /// <param name="key">缓存Key</param>
        /// <param name="value">缓存Value</param>
        /// <param name="expiresSliding">滑动过期时长（如果在过期时间内有操作，则以当前时间点延长过期时间,Redis中无效）</param>
        /// <param name="expiressAbsoulte">绝对过期时长</param>
        /// <returns></returns>
        public bool Add(string key, object value, TimeSpan expiresSliding, TimeSpan expiressAbsoulte)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            string _value = "";
            if (value.GetType() == typeof(string))
                _value = value.ToString();
            else
                _value = JsonSerializer.Serialize(value, _jsonOptions);
            return _database.StringSet(GetKeyForRedis(key), Encoding.UTF8.GetBytes(_value), expiressAbsoulte);
        }
        /// <summary>
        /// 添加缓存
        /// </summary>
        /// <param name="key">缓存Key</param>
        /// <param name="value">缓存Value</param>
        /// <param name="expiresIn">缓存时长</param>
        /// <param name="isSliding">是否滑动过期（如果在过期时间内有操作，则以当前时间点延长过期时间,Redis中无效）</param>
        /// <returns></returns>
        public bool Add(string key, object value, TimeSpan expiresIn, bool isSliding = false)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            string _value = "";
            if (value.GetType() == typeof(string))
                _value = value.ToString();
            else
                _value = JsonSerializer.Serialize(value, _jsonOptions);

            return _database.StringSet(GetKeyForRedis(key), Encoding.UTF8.GetBytes(_value), expiresIn);
        }


        public Task<bool> AddAsync(string key, object value)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            string _value = "";
            if (value.GetType() == typeof(string))
                _value = value.ToString();
            else
                _value = JsonSerializer.Serialize(value, _jsonOptions);

            return _database.StringSetAsync(GetKeyForRedis(key), Encoding.UTF8.GetBytes(_value));
        }

        public Task<bool> AddAsync(string key, object value, TimeSpan expiresSliding, TimeSpan expiressAbsoulte)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            string _value = "";
            if (value.GetType() == typeof(string))
                _value = value.ToString();
            else
                _value = JsonSerializer.Serialize(value, _jsonOptions);

            return _database.StringSetAsync(GetKeyForRedis(key), Encoding.UTF8.GetBytes(_value), expiressAbsoulte);
        }

        public Task<bool> AddAsync(string key, object value, TimeSpan expiresIn, bool isSliding = false)
        {

            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            string _value = "";
            if (value.GetType() == typeof(string))
                _value = value.ToString();
            else
                _value = JsonSerializer.Serialize(value, _jsonOptions);

            return _database.StringSetAsync(GetKeyForRedis(key), Encoding.UTF8.GetBytes(_value), expiresIn);
        }

        #endregion

        #region 删除缓存
        /// <summary>
        /// 删除缓存
        /// </summary>
        /// <param name="key">缓存Key</param>
        /// <returns></returns>
        public bool Remove(string key)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }
            return _database.KeyDelete(GetKeyForRedis(key));
        }
        /// <summary>
        /// 批量删除缓存
        /// </summary>
        /// <param name="key">缓存Key集合</param>
        /// <returns></returns>
        public void RemoveAll(IEnumerable<string> keys)
        {
            if (keys == null)
            {
                throw new ArgumentNullException(nameof(keys));
            }

            keys.ToList().ForEach(item => Remove(item));
        }


        public Task<bool> RemoveAsync(string key)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }
            return _database.KeyDeleteAsync(GetKeyForRedis(key));
        }

        public Task RemoveAllAsync(IEnumerable<string> keys)
        {
            //if (keys == null)
            //{
            throw new ArgumentNullException(nameof(keys));
            //}

            //keys.ToList().ForEach(item => RemoveAsync(item));
        }

        /// <summary>
        /// 使用通配符找出所有的key然后逐个删除
        /// </summary>
        /// <param name="pattern">通配符，前后会自动增加*号，比如Admin:UserInfo:Id</param>
        public virtual void RemoveByPattern(string pattern)
        {
            foreach (var ep in _connection.GetEndPoints())
            {
                var server = _connection.GetServer(ep);
                var keys = server.Keys(pattern: "*" + pattern + "*", database: _database.Database);
                foreach (var key in keys)
                    _database.KeyDelete(key);
            }
        }


        /// <summary>
        /// 删除所有缓存
        /// </summary>
        public void RemoveCacheAll()
        {
            RemoveByPattern("");
        }

        /// <summary>
        /// 删除模糊匹配缓存【仅支持Redis】，通过命令删除缓存，主要是用于前缀一样的情况。
        /// </summary>
        /// <param name="keyPattern">模糊key值，需要自己写通配符*号，比如：Admin:UserInfo:Id:*</param>
        /// <returns></returns>
        public bool RemoveCacheByKey(string keyPattern)
        {
            var script = "return redis.call('keys',@pattern)";
            var prepared = LuaScript.Prepare(script);
            var cacheResult = _database.ScriptEvaluate(prepared, new { pattern = GetKeyForRedis(keyPattern) });
            if (!cacheResult.IsNull)
            {
                foreach (var item in (string[])cacheResult)
                {
                    _database.KeyDelete(item);
                }
            }
            return true;
        }

        #endregion

        #region 获取缓存

        /// <summary>
        /// 获取缓存
        /// </summary>
        /// <param name="key">缓存Key</param>
        /// <returns></returns>
        public T Get<T>(string key) where T : class
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            var value = _database.StringGet(GetKeyForRedis(key));

            if (!value.HasValue)
            {
                return null;
            }
            if (typeof(T) == typeof(string))
            {
                return value.ToString() as T;
            }
            return JsonSerializer.Deserialize<T>(value, _jsonOptions);
        }
        /// <summary>
        /// 获取缓存
        /// </summary>
        /// <param name="key">缓存Key</param>
        /// <returns></returns>
        public object Get(string key)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            var value = _database.StringGet(GetKeyForRedis(key));

            if (!value.HasValue)
            {
                return null;
            }
            return value;
            //return JsonSerializer.Deserialize<object>(value, _jsonOptions);
            //string json = value.ToString();
            //return Newtonsoft.Json.JsonConvert.DeserializeObject(json);
        }
        /// <summary>
        /// 获取缓存集合
        /// </summary>
        /// <param name="keys">缓存Key集合</param>
        /// <returns></returns>
        public IDictionary<string, object> GetAll(IEnumerable<string> keys)
        {
            if (keys == null)
            {
                throw new ArgumentNullException(nameof(keys));
            }
            var dict = new Dictionary<string, object>();

            keys.ToList().ForEach(item => dict.Add(item, Get(GetKeyForRedis(item))));

            return dict;
        }


        public Task<T> GetAsync<T>(string key) where T : class
        {
            throw new NotImplementedException();
        }

        public Task<object> GetAsync(string key)
        {
            throw new NotImplementedException();
        }

        public Task<IDictionary<string, object>> GetAllAsync(IEnumerable<string> keys)
        {

            throw new ArgumentNullException(nameof(keys));
        }
        #endregion

        #region 修改缓存
        /// <summary>
        /// 修改缓存
        /// </summary>
        /// <param name="key">缓存Key</param>
        /// <param name="value">新的缓存Value</param>
        /// <returns></returns>
        public bool Replace(string key, object value)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            if (Exists(key))
                if (!Remove(key))
                    return false;

            return Add(key, value);

        }
        /// <summary>
        /// 修改缓存
        /// </summary>
        /// <param name="key">缓存Key</param>
        /// <param name="value">新的缓存Value</param>
        /// <param name="expiresSliding">滑动过期时长（如果在过期时间内有操作，则以当前时间点延长过期时间）</param>
        /// <param name="expiressAbsoulte">绝对过期时长</param>
        /// <returns></returns>
        public bool Replace(string key, object value, TimeSpan expiresSliding, TimeSpan expiressAbsoulte)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            if (Exists(key))
                if (!Remove(key))
                    return false;

            return Add(key, value, expiresSliding, expiressAbsoulte);
        }
        /// <summary>
        /// 修改缓存
        /// </summary>
        /// <param name="key">缓存Key</param>
        /// <param name="value">新的缓存Value</param>
        /// <param name="expiresIn">缓存时长</param>
        /// <param name="isSliding">是否滑动过期（如果在过期时间内有操作，则以当前时间点延长过期时间）</param>
        /// <returns></returns>
        public bool Replace(string key, object value, TimeSpan expiresIn, bool isSliding = false)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            if (Exists(key))
                if (!Remove(key)) return false;

            return Add(key, value, expiresIn, isSliding);
        }


        public Task<bool> ReplaceAsync(string key, object value)
        {
            throw new NotImplementedException();
        }

        public Task<bool> ReplaceAsync(string key, object value, TimeSpan expiresSliding, TimeSpan expiressAbsoulte)
        {
            throw new NotImplementedException();
        }

        public Task<bool> ReplaceAsync(string key, object value, TimeSpan expiresIn, bool isSliding = false)
        {
            throw new NotImplementedException();
        }
        #endregion

        #region 其他

        #region 步长1的递增、递减
        /// <summary>
        /// 为数字增长val
        /// </summary>
        /// <param name="key">缓存键值</param>
        /// <param name="value">缓存值</param>
        /// <param name="cacheTime">过期时间</param>
        public double StringIncrement(string key, double value, TimeSpan? cacheTime)
        {
            if (cacheTime != null)
            {
                // 小于0时删除
                if (((TimeSpan)cacheTime).TotalSeconds < 0)
                {
                    Remove(key);

                    return 0;
                }
                // 等于0时永久
                else if (((TimeSpan)cacheTime).TotalSeconds == 0)
                {
                    cacheTime = null;
                }
            }

            var result = _database.StringIncrement(GetKeyForRedis(key), value);
            var keResult = _database.KeyExpire(GetKeyForRedis(key), cacheTime);
            return result;
        }

        /// <summary>
        /// 为数字减少val
        /// </summary>
        /// <param name="key">缓存键值</param>
        /// <param name="value">缓存值</param>
        /// <param name="cacheTime">过期时间</param>
        public double StringDecrement(string key, double value, TimeSpan? cacheTime)
        {
            if (cacheTime != null)
            {
                // 小于0时删除
                if (((TimeSpan)cacheTime).TotalSeconds < 0)
                {
                    Remove(key);

                    return 0;
                }
                // 等于0时永久
                else if (((TimeSpan)cacheTime).TotalSeconds == 0)
                {
                    cacheTime = null;
                }
            }

            var result = _database.StringDecrement(GetKeyForRedis(key), value);
            _database.KeyExpire(GetKeyForRedis(key), cacheTime);
            return result;
        }

        #endregion

        #region 分布式锁

        /// <summary>
        /// redis加锁操作
        /// </summary>
        /// <param name="key">锁的名称，不可重复</param>
        /// <param name="expireTimeSeconds">锁过期时间</param>
        public void Lock(string key, int expireTimeSeconds = 10)
        {
            try
            {
                while (true)
                {
                    expireTimeSeconds = expireTimeSeconds > 30 ? 30 : expireTimeSeconds;
                    bool lockflag = _database.LockTake(GetKeyForRedis(key), "CbmnetLock", TimeSpan.FromSeconds(expireTimeSeconds));
                    if (lockflag)
                    {
                        break;
                    }
                }
            }
            catch (Exception ex)
            {
                throw new Exception($"Redis加锁异常:原因{ex.Message}");
            }
        }

        /// <summary>
        /// redis解锁操作
        /// </summary>
        /// <param name="key">锁的名称</param>
        /// <returns></returns>
        public bool UnLock(string key)
        {
            try
            {
                return _database.LockRelease(GetKeyForRedis(key), "CbmnetLock");
            }
            catch (Exception ex)
            {
                throw new Exception($"Redis加锁异常:原因{ex.Message}");
            }
        }

        #endregion

        #endregion

        public void Dispose()
        {
            if (_connection != null)
                _connection.Dispose();
            GC.SuppressFinalize(this);
        }
    }
}